﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;

namespace GeoParser
{
	/// <summary>
	/// Program entry point; implements a simple command-line interface.
	/// </summary>
	class Program
	{
		static void Main(string[] args)
		{
			switch (args[0])
			{
				case "xpath":
					XPath(args[1], args[2], args[3]);
					break;
				case "makeroute":
					MakeRoute(args[1], args[2], args.Length >= 4 ? args[3] : null);
					break;
				case "near":
					Near(args[1], args[2], args[3], args[4]);
					break;
				case "infolder":
					InFolder = args[1];
					break;
				case "outfolder":
					OutFolder = args[1];
					break;
				case "tracklength":
					if (args.Length >= 6)
						TrackLength(args[1], args[2], args[3], args[4], args[5]);
					else
						TrackLength(args[1], null, null, null, null);
					break;
				case "merge":
					Merge(args[1], args.Skip(2));
					break;
				case "setname":
					SetName(args[1]);
					break;
			}
		}

		private static void SetName(string inFileName)
		{
			var gps = GpsExchange.Load(MakeInPath(inFileName));
			string newFileName = Path.Combine(Path.GetDirectoryName(inFileName), gps.Name);
			File.Move(inFileName, newFileName);
		}

		private static string InFolder
		{
			get
			{
				return Properties.Settings.Default.InFolder;
			}
			set
			{
				Properties.Settings.Default.InFolder = value;
				Properties.Settings.Default.Save();
			}
		}

		private static string OutFolder
		{
			get
			{
				return Properties.Settings.Default.OutFolder;
			}
			set
			{
				Properties.Settings.Default.OutFolder = value;
				Properties.Settings.Default.Save();
			}
		}

		private static string MakeInPath(string part)
		{
			if (Path.IsPathRooted(part))
				return part;
			else
				return Path.Combine(InFolder, part);
		}

		private static string MakeOutPath(string part)
		{
			if (Path.IsPathRooted(part))
				return part;
			else
				return Path.Combine(OutFolder, part);
		}

		/// <summary>
		/// Finds all the waypoints within a given radius from a given point.
		/// </summary>
		/// <param name="inFile">Input file name.</param>
		/// <param name="lat">Latitude of centre point.</param>
		/// <param name="lon">Longitude of centre point.</param>
		/// <param name="radius">Radius of inclusion.</param>
		private static void Near(string inFile, string lat, string lon, string radius)
		{
			var gps = GpsExchange.Load(MakeInPath(inFile));
			var point = new GeoPoint(lat, lon);
			var range = new Distance(double.Parse(radius), Distance.Unit.Miles);

			foreach (Waypoint waypoint in gps.WaypointsNear(point, range))
			{
				Console.WriteLine("{0} [{1}]", waypoint, GeoPoint.DistanceBetween(waypoint, point));
			}
		}

		/// <summary>
		/// Creates a route from a subset of waypoints whose name starts with <paramref name="name"/>.  The order of the route
		/// is decided by jumping to the closest unvisited waypoint, starting either from the waypoint specified or the first
		/// in the input file.
		/// </summary>
		/// <param name="inFile">Name of input file.</param>
		/// <param name="name">Start of waypoint name to match, and also used for output file name.</param>
		/// <param name="start">Waypoint to use as starting position, otherwise start will be </param>
		private static void MakeRoute(string inFile, string name, string start)
		{
			var gpsIn = GpsExchange.Load(MakeInPath(inFile));

			var gpsOut = new GpsExchange();

			string desc = "Walk around " + name;

			var waypoints = new List<Waypoint>(gpsIn.WaypointsDescribed(name));
			Route route;

			if (!string.IsNullOrEmpty(start))
				waypoints = new List<Waypoint>(Waypoint.StartFrom(waypoints, start));

			route = new Route(waypoints);
			route.Name = name;
			route.Description = desc;

			foreach (Waypoint waypoint in waypoints)
				Console.WriteLine(waypoint);

			gpsOut.Add(route);
			gpsOut.Save(MakeOutPath(name + ".gpx"));
		}

		/// <summary>
		/// Run an xpath query to filter out waypoints.
		/// </summary>
		/// <param name="inFile">Name of input file.</param>
		/// <param name="xPath">XPath query - all matching waypoints will be removed.</param>
		/// <param name="outFile">Name of outputfile.</param>
		private static void XPath(string inFile, string xPath, string outFile)
		{
			var gpsIn = GpsExchange.Load(MakeInPath(inFile));

			XNamespace groundspeak = "groundspeak";

			// e.g. "gs:cache/gs:type != 'Traditional Cache'"
			var unsorted = new List<Waypoint>(gpsIn.WayPointsMatching(xPath));
			gpsIn.Remove(unsorted);

			gpsIn.Save(MakeOutPath(outFile));
		}
	
		/// <summary>
		/// Calculates the length of a recorded track.
		/// </summary>
		/// <param name="inFile">Name of input file.</param>
		/// <param name="latStart">Optional, starting point latitude.</param>
		/// <param name="lonStart">Optional, starting point longitude.</param>
		/// <param name="latEnd">Optional, finishing point latitude.</param>
		/// <param name="lonEnd">Optional, finishing point longitude.</param>
		private static void TrackLength(string inFile, string latStart, string lonStart, string latEnd, string lonEnd)
		{
			var gpsIn = GpsExchange.Load(MakeInPath(inFile));

			if (!string.IsNullOrEmpty(latStart))
			{
				var start = new GeoPoint(latStart, lonStart);
				var end = new GeoPoint(latEnd, lonEnd);

				foreach (Track track in gpsIn.Tracks)
					Console.WriteLine("{0} {1}", track.LengthBetween(start, end), track.TimeBetween(start, end));
			}
			else
			{
				foreach (Track track in gpsIn.Tracks)
					Console.WriteLine("{0} {1}", track.Length, track.Time);
			}
		}

		/// <summary>
		/// Merge multiple GPS Exchange files together.  Starts with the first and adds unique waypoints from all the others.
		/// </summary>
		/// <param name="outFile">The name of the output file.</param>
		/// <param name="inFileNames">The list of file names, wildcards permitted.</param>
		private static void Merge(string outFile, IEnumerable<string> inFileNames)
		{
			var fileNames = inFileNames.SelectMany(x => Directory.GetFiles(MakeInPath(x)));
			
			var gpsOut = GpsExchange.Load(fileNames.First());

			foreach (string fileName in fileNames)
			{
				gpsOut.Add(GpsExchange.Load(fileName));
			}

			gpsOut.Save(MakeOutPath(outFile));
		}
	}
}